package com.ochnas.volunteersignup.service;

import static com.ochnas.volunteersignup.ui.SharedConstants.SORT_BY_ASCENDING_NAME;
import static com.ochnas.volunteersignup.ui.SharedConstants.SORT_BY_ASCENDING_TIME;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.jws.WebService;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.SessionCallback;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.orm.jpa.support.JpaDaoSupport;

import com.ochnas.volunteersignup.domain.Event;
import com.ochnas.volunteersignup.domain.FilledVolunteerRequisition;
import com.ochnas.volunteersignup.domain.Position;
import com.ochnas.volunteersignup.domain.Time;
import com.ochnas.volunteersignup.domain.TimeSlot;
import com.ochnas.volunteersignup.domain.User;
import static com.ochnas.volunteersignup.ui.SharedConstants.*;
@WebService(endpointInterface = "com.ochnas.volunteersignup.service.VolunteerService")
public class VolunteerServiceImpl extends JpaDaoSupport implements
		VolunteerService {
	
	public static final String POSITION_TOPIC = "NotifySignupTopic";
	
	private JmsTemplate jmsTemplate;
	private MailSender mailSender;	
	
	private Comparator<TimeSlot> comparator = new Comparator<TimeSlot>() {

		@Override
		public int compare(TimeSlot t1, TimeSlot t2) {
			Time t1StartTime = t1.getStartTime();
			Time t2StartTimie = t2.getStartTime();
			if (t1StartTime.getHourOfDay() > t2StartTimie.getHourOfDay()) {
				return 1;
			} else if (t1StartTime.getHourOfDay() < t2StartTimie.getHourOfDay()) {
				return -1;
			} else {
				if (t1StartTime.getMinutes() > t2StartTimie.getMinutes()) {
					return 1;
				} else if (t1StartTime.getMinutes() < t2StartTimie.getMinutes()) {
					return -1;
				}
			}
			return 1;
		}
		
	};
	
	public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
	
	public void setMailSender(MailSender mailSender) {
		this.mailSender = mailSender;
	}
	
	@Override
	public List<Position> findAllFilledPositionsByEvent(Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		String queryString = "select f from FilledVolunteerRequisition f where f.event = ?1";
		List resultList = getJpaTemplate().find(queryString, storedEvent);
		List<Position> positions = new ArrayList<Position>();
		for (Object o: resultList) {
			Position p = getJpaTemplate().find(Position.class, ((FilledVolunteerRequisition)o).getPosition().getId());
			if (p != null) {
				positions.add(p);
			}
		}
		return positions;
	}

	
	
	@Override
	public List<TimeSlot> findAllFilledTimeSlotsByEvent(Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		String queryString = "select f from FilledVolunteerRequisition f where f.event = ?1 AND f.timeSlot IS NOT NULL";
		List resultList = getJpaTemplate().find(queryString, storedEvent);
		List<TimeSlot> timeSlots = new ArrayList<TimeSlot>();
		for (Object o: resultList) {
			TimeSlot t = getJpaTemplate().find(TimeSlot.class, ((FilledVolunteerRequisition)o).getTimeSlot().getId());
			if (t != null) {
				timeSlots.add(t);
			}
		}
		return timeSlots;
	}

	@Override
	public List<Event> findAllFutureEvents(Date startDate) {
		String queryString = "select e from Event e where e.startDate > ?1";
		List resultList = getJpaTemplate().find(queryString, startDate);
		return resultList;
	}

	@Override
	public List<Position> findAllOpenPositionsByEvent(Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		List eventPositions = getJpaTemplate().find("select p from Position p where p.event = ?1", storedEvent);
		List<Position> openPositions = new ArrayList<Position>();
		for (Object p: eventPositions) {
			Position position = getJpaTemplate().find(Position.class, ((Position)p).getId());
			int numOfRequiredVolunteersForPosition = position.getRequiredNumberOfVolunteers();
			List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
			position.setTimeSlots(timeSlotResults);
			if (timeSlotResults.size() > 0) { // position with time slots
				for (Object o: timeSlotResults) {
					int numOfRequiredVolunteersForTimeSlot = ((TimeSlot) o).getQuantityOfVolunteers();
					List resultFilledTimeSlots = getJpaTemplate().
						find("select f from FilledVolunteerRequisition f where f.timeSlot = ?1", o);
					if (resultFilledTimeSlots.size() < numOfRequiredVolunteersForTimeSlot) {
						if (!openPositions.contains(position))	openPositions.add(position);
					}
				}
			} else { // position without time slots	
				List resultList = getJpaTemplate().find("select f from FilledVolunteerRequisition f where f.position = ?1", position);
				if (resultList.size() < numOfRequiredVolunteersForPosition) {
					if (!openPositions.contains(position))	openPositions.add(position);
				}
			}
		}
		Position[] positions = openPositions.toArray(new Position[0]);
		Arrays.sort(positions, SORT_BY_ASCENDING_NAME);
		return Arrays.asList(positions);
	}

	@Override
	public List<TimeSlot> findAllOpenTimeSlotsByEvent(Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		List eventPositions = getJpaTemplate().find("select p from Position p where p.event = ?1", storedEvent);
		List<TimeSlot> openTimeSlots = new ArrayList<TimeSlot>();
		for (Object p: eventPositions) {
			Position position = getJpaTemplate().find(Position.class, ((Position)p).getId());
			List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
			if (timeSlotResults.size() > 0) { // position with time slots
				for (Object o: timeSlotResults) {
					TimeSlot timeSlot = getJpaTemplate().find(TimeSlot.class, ((TimeSlot)o).getId());
					int numOfRequiredVolunteersForTimeSlot = ((TimeSlot) o).getQuantityOfVolunteers();
					List resultFilledTimeSlots = getJpaTemplate().
						find("select f from FilledVolunteerRequisition f where f.timeSlot = ?1", o);
					if (resultFilledTimeSlots.size() < numOfRequiredVolunteersForTimeSlot) {
						openTimeSlots.add(timeSlot);
					}
				}
			} 
		}
		
		TimeSlot[] timeSlots = openTimeSlots.toArray(new TimeSlot[0]);
		Arrays.sort(timeSlots, SORT_BY_ASCENDING_TIME);
		return Arrays.asList(timeSlots);
	}
	
	@Override
	public List<TimeSlot> findAllOpenTimeSlotsByPosition(Long positionId) {
		List<TimeSlot> openTimeSlots = new ArrayList<TimeSlot>();
		Position position = getJpaTemplate().find(Position.class, positionId);
		List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
		if (timeSlotResults.size() > 0) { // position with time slots
			for (Object o: timeSlotResults) {
				TimeSlot timeSlot = getJpaTemplate().find(TimeSlot.class, ((TimeSlot)o).getId());
				int numOfRequiredVolunteersForTimeSlot = ((TimeSlot) o).getQuantityOfVolunteers();
				List resultFilledTimeSlots = getJpaTemplate().
					find("select f from FilledVolunteerRequisition f where f.timeSlot = ?1", o);
				if (resultFilledTimeSlots.size() < numOfRequiredVolunteersForTimeSlot) {
					openTimeSlots.add(timeSlot);
				}
			}
		} 
		
		TimeSlot[] timeSlots = openTimeSlots.toArray(new TimeSlot[0]);
		Arrays.sort(timeSlots, SORT_BY_ASCENDING_TIME);
		return Arrays.asList(timeSlots);
	}

	@Override
	public List<Position> findAllPositionsByEvent(Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		List positions = getJpaTemplate().find("select p from Position p where p.event =?", storedEvent);
		return positions;
	}

	@Override
	public String findDetailedVolunteerInfoByEvent(Long eventId) {
		StringBuffer strBuff = new StringBuffer("Detailed Volunteer Information For Event");
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		// 2nd line
		strBuff.append("\nEvent: ");
		strBuff.append(storedEvent.getName());
		// 3rd line
		strBuff.append("\nDate: ");
		strBuff.append(storedEvent.getStartDate());
		strBuff.append(" to ");
		strBuff.append(storedEvent.getEndDate());
		// 4th line
		strBuff.append("\nTime ");
		strBuff.append(storedEvent.getStartTime());
		strBuff.append(" to ");
		strBuff.append(storedEvent.getEndTime());
		List eventPositions = getJpaTemplate().find("select p from Position p where p.event = ?1", storedEvent);
		for (Object p: eventPositions) {
			Position position = getJpaTemplate().find(Position.class, ((Position)p).getId());
			List resultList = getJpaTemplate().find("select f from FilledVolunteerRequisition f where f.position = ?1", position);
			if (!resultList.isEmpty()) {
				strBuff.append("\nPosition: ");
				strBuff.append(position.getName());
			}
			List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
			
			if (timeSlotResults.size() > 0) { // position with time slots
				Collections.sort(timeSlotResults, comparator);
				for (Object o: timeSlotResults) {
					List resultFilledTimeSlots = getJpaTemplate().
						find("select f from FilledVolunteerRequisition f where f.timeSlot = ?1", o);
					if (!resultFilledTimeSlots.isEmpty()) {
						TimeSlot timeSlot = (TimeSlot) o;
						strBuff.append("\n\t");
						strBuff.append(timeSlot.getStartTime().toString());
						strBuff.append(" to ");
						strBuff.append(timeSlot.getEndTime().toString());
					}
					for (Object ft: resultFilledTimeSlots) {
						FilledVolunteerRequisition filledReq = getJpaTemplate().find(FilledVolunteerRequisition.class, 
								((FilledVolunteerRequisition)ft).getId());
						User fpUser = getJpaTemplate().find(User.class, filledReq.getUser().getId());
						strBuff.append("\n\t\t");
						strBuff.append(fpUser.getFirstName());
						strBuff.append(" ");
						strBuff.append(fpUser.getLastName());
						strBuff.append("\n\t\t\tEmail: ");
						strBuff.append(fpUser.getEmailAddress());
						strBuff.append("\n\t\t\tContact Phone Number: ");
						strBuff.append(fpUser.getContactPhoneNumber());		
					}
				}
			} else { // position without time slots	
				for (Object fp: resultList) {
					FilledVolunteerRequisition filledReq = getJpaTemplate().find(FilledVolunteerRequisition.class, 
							((FilledVolunteerRequisition)fp).getId());
					User fpUser = getJpaTemplate().find(User.class, filledReq.getUser().getId());
					strBuff.append("\n\t");
					strBuff.append(fpUser.getFirstName());
					strBuff.append(" ");
					strBuff.append(fpUser.getLastName());
					strBuff.append("\n\t\tEmail: ");
					strBuff.append(fpUser.getEmailAddress());
					strBuff.append("\n\t\tContact Phone Number: ");
					strBuff.append(fpUser.getContactPhoneNumber());					
				}
			}
		}
		return strBuff.toString();
	}

	@Override
	public FilledVolunteerRequisition registerForEventByPosition(Position p,
			User user) {
		Position storedPosition = getJpaTemplate().find(Position.class, p.getId());
		Event storedEvent = getJpaTemplate().find(Event.class, storedPosition.getEvent().getId());
		User storedUser = getJpaTemplate().find(User.class, user.getId());
		List resultFilledPositions = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.user = ?1 AND f.position = ?2 AND f.timeSlot IS NULL", storedUser, storedPosition);
		if (!resultFilledPositions.isEmpty()) {
			throw new NonUniqueResultException("You have already volunteered for position '" + storedPosition.getName() + "' at event '" + storedEvent.getName()+"'.");
		}
		
		// check if filled
		resultFilledPositions = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.position = ?1 AND f.timeSlot IS NULL", storedPosition);
		
		if (resultFilledPositions.size() == storedPosition.getRequiredNumberOfVolunteers()) {
			throw new NoResultException("Unable to volunteer for position '"+storedPosition.getName()+"' at event '" + storedEvent.getName()+"'. The position has been filled.");
		}
		
		FilledVolunteerRequisition fvr = new FilledVolunteerRequisition(storedUser, storedEvent, storedPosition);
		getJpaTemplate().persist(fvr);
		fvr.setEvent(storedEvent);
		fvr.setPosition(storedPosition);
		fvr.setUser(storedUser);
		final FilledVolunteerRequisition finalFVR = fvr;
				
		/* Send a message to a JMS topic information about the
		 * event, position, and volunteering user.
		 */ 
		jmsTemplate.execute(new SessionCallback() {
			public Object doInJms(Session session) throws JMSException {
				session.createProducer(session.createTopic(POSITION_TOPIC)).
				send(session.createTextMessage(finalFVR.toXml()));
				return null;
			}
		});
		
		String link = "http://localhost:8080/confirmVolunteer.jsp";
		
		SimpleMailMessage msg = new SimpleMailMessage();
		msg.setTo(storedUser.getEmailAddress());
		msg.setFrom("owner@ochnas.com");
		msg.setSubject("Thank You!");
		msg.setText("Thank You! You volunteer signed up was successful.  Click the following link for details: ");
		msg.setText(
				"<html><body><br/><a href='"+
				link + "'></a></body></html>");
		mailSender.send(msg);
				
		return fvr;
	}

	@Override
	public FilledVolunteerRequisition registerForEventByTimeSlot(TimeSlot ts,
			User user) {
		TimeSlot storedTimeSlot = getJpaTemplate().find(TimeSlot.class, ts.getId());
		Position storedPosition = getJpaTemplate().find(Position.class, storedTimeSlot.getPosition().getId());
		Event storedEvent = getJpaTemplate().find(Event.class, storedPosition.getEvent().getId());
		User storedUser = getJpaTemplate().find(User.class, user.getId());
		List resultFilledTimeSlots = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.user = ?1 AND f.position = ?2 AND f.timeSlot = ?3", storedUser, storedPosition, storedTimeSlot);
		if (!resultFilledTimeSlots.isEmpty()) {
			throw new NonUniqueResultException("You have already volunteered for time slot '"+
					storedTimeSlot.getStartTime().toString() +" to "+
					storedTimeSlot.getEndTime().toString()+
					"' for position '" + storedPosition.getName() + "' at event '" + storedEvent.getName()+"'.");
		}

		// check if filled
		resultFilledTimeSlots = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.position = ?1 AND f.timeSlot = ?2", storedPosition, storedTimeSlot);
		
		if (resultFilledTimeSlots.size() == storedTimeSlot.getQuantityOfVolunteers()) {
			throw new NoResultException("Unable to volunteer for time slot '"+
					storedTimeSlot.getStartTime().toString() +" to "+
					storedTimeSlot.getEndTime().toString()+
					"' for position '" + storedPosition.getName() + "' at event '" + storedEvent.getName()+"'. The time slot has been filled.");
		}
		
		FilledVolunteerRequisition fvr = new FilledVolunteerRequisition(storedUser, storedEvent, storedPosition, storedTimeSlot);
		getJpaTemplate().persist(fvr);
		fvr.setEvent(storedEvent);
		fvr.setPosition(storedPosition);
		fvr.setUser(storedUser);
		fvr.setTimeSlot(storedTimeSlot);
		return fvr;
	}

	@Override
	public void unregisterForEventByPosition(Position p, User user) {
		Position storedPosition = getJpaTemplate().find(Position.class, p.getId());
		User storedUser = getJpaTemplate().find(User.class, user.getId());
		
		List resultList = getJpaTemplate().find("select f from  FilledVolunteerRequisition f where f.position = ?1 AND f.user = ?2 AND f.timeSlot IS NULL",
				storedPosition, storedUser);
		
		if (!resultList.isEmpty()) {
			FilledVolunteerRequisition fvr = (FilledVolunteerRequisition) resultList.get(0);
			getJpaTemplate().remove(fvr);
		}
		
		
	}

	@Override
	public void unregisterForEventByTimeSlot(TimeSlot ts, User user) {
		Position storedPosition = getJpaTemplate().find(Position.class, ts.getPosition().getId());
		TimeSlot storedTimeSlot = getJpaTemplate().find(TimeSlot.class, ts.getId());
		User storedUser = getJpaTemplate().find(User.class, user.getId());
		
		List resultList = getJpaTemplate().find("select f from  FilledVolunteerRequisition f where f.position = ?1 AND f.timeSlot = ?2 AND f.user = ?3",
				storedPosition, storedTimeSlot, storedUser);
		if (!resultList.isEmpty()) {
			FilledVolunteerRequisition fvr = (FilledVolunteerRequisition) resultList.get(0);
			getJpaTemplate().remove(fvr);
		}
	}



	@Override
	public SortedMap<Long, List<FilledVolunteerRequisition>> findVolunteersForPositionsWithoutTimeSlotsByEvent(
			Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		String queryString = "select p from Position p where p.event =?";
		List positions = getJpaTemplate().find(queryString, storedEvent);
		SortedMap<Long, List<FilledVolunteerRequisition>> mapOfPositionVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();
		for (Object p: positions) {
			Position position = (Position) p;
			queryString = "select f from FilledVolunteerRequisition f where f.event = ?1 AND f.position = ?2 AND f.timeSlot IS NULL";
			List resultList = getJpaTemplate().find(queryString, storedEvent, position);
			if (resultList.isEmpty()) {
				mapOfPositionVolunteers.put(position.getId(),  new ArrayList<FilledVolunteerRequisition>());
			} else {
				for (Object v: resultList) {
					FilledVolunteerRequisition volunteer = (FilledVolunteerRequisition) v;
					if (mapOfPositionVolunteers.containsKey(position.getId())) {
						List<FilledVolunteerRequisition> volunteers = mapOfPositionVolunteers.get(position.getId());
						volunteers.add(volunteer);
					} else {
						List<FilledVolunteerRequisition> volunteers = new ArrayList<FilledVolunteerRequisition>();
						volunteers.add(volunteer);
						mapOfPositionVolunteers.put(position.getId(),  volunteers);
					}		
				}
			}
		}
		return mapOfPositionVolunteers;
	}



	@Override
	public SortedMap<Long, List<FilledVolunteerRequisition>> findVolunteersForTimeSlotsByEvent(
			Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		String queryString = "select p from Position p where p.event =?";
		List positions = getJpaTemplate().find(queryString, storedEvent);
		SortedMap<Long, List<FilledVolunteerRequisition>> mapOfTimeSlotVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();	
		for (Object p: positions) {
			Position position = (Position) p;
			queryString = "select t from TimeSlot t where t.position = ?1";
			List timeSlots = getJpaTemplate().find(queryString, position);
			for (Object t: timeSlots) {
				TimeSlot timeSlot = (TimeSlot)t;
				queryString = "select f from FilledVolunteerRequisition f where f.timeSlot =?1";
				List resultList = getJpaTemplate().find(queryString, timeSlot);
				if (resultList.isEmpty()) {
					mapOfTimeSlotVolunteers.put(timeSlot.getId(), new ArrayList<FilledVolunteerRequisition>());
				} else {
					for (Object v: resultList) {
						FilledVolunteerRequisition volunteer = (FilledVolunteerRequisition) v;
						if (mapOfTimeSlotVolunteers.containsKey(timeSlot.getId())) {
							List<FilledVolunteerRequisition> volunteers = mapOfTimeSlotVolunteers.get(timeSlot.getId());
							volunteers.add(volunteer);
						} else {
							List<FilledVolunteerRequisition> volunteers = new ArrayList<FilledVolunteerRequisition>();
							volunteers.add(volunteer);
							mapOfTimeSlotVolunteers.put(timeSlot.getId(),  volunteers);
						}
					}
				}
			}
		}
		return mapOfTimeSlotVolunteers;		
	}



	@SuppressWarnings("unchecked")
	@Override
	public Map<Long, List<TimeSlot>> findMapOfOpentTimeSlotsForPositionByEvent(
			Long eventId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		String queryString = "select p from Position p where p.event =?";
		List positions = getJpaTemplate().find(queryString, storedEvent);
		Map<Long, List<TimeSlot>> openTimeSlotsByPosition = new HashMap<Long, List<TimeSlot>>();
		for (Object p: positions) {
			Position position = (Position) p;

			List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
			if (timeSlotResults.size() > 0) { // position with time slots
				for (Object o: timeSlotResults) {
					TimeSlot timeSlot = getJpaTemplate().find(TimeSlot.class, ((TimeSlot)o).getId());
					int numOfRequiredVolunteersForTimeSlot = ((TimeSlot) o).getQuantityOfVolunteers();
					List resultFilledTimeSlots = getJpaTemplate().
						find("select f from FilledVolunteerRequisition f where f.timeSlot = ?1", o);
					if (resultFilledTimeSlots.size() < numOfRequiredVolunteersForTimeSlot) {
						if (!openTimeSlotsByPosition.containsKey(position.getId())) {
							openTimeSlotsByPosition.put(position.getId(), new ArrayList<TimeSlot>());
						}
						List<TimeSlot> openSlots = openTimeSlotsByPosition.get(position.getId());
						openSlots.add(timeSlot);
						TimeSlot[] timeSlots = openSlots.toArray(new TimeSlot[0]);
						Arrays.sort(timeSlots, SORT_BY_ASCENDING_TIME);
						openSlots.clear();
						openSlots.addAll(Arrays.asList(timeSlots));
					}
				}
			}  
		}
		return openTimeSlotsByPosition;
	}



	@Override
	public List<TimeSlot> findTimeSlotsByPosition(Long positionId) {
		List<TimeSlot> openTimeSlots = new ArrayList<TimeSlot>();
		Position position = getJpaTemplate().find(Position.class, positionId);
		List timeSlotResults = getJpaTemplate().find("select t from TimeSlot t where t.position = ?1", position);
		if (timeSlotResults.size() > 0) { // position with time slots
			for (Object o: timeSlotResults) {
				TimeSlot timeSlot = getJpaTemplate().find(TimeSlot.class, ((TimeSlot)o).getId());
				openTimeSlots.add(timeSlot);
			}
		} 
		
		TimeSlot[] timeSlots = openTimeSlots.toArray(new TimeSlot[0]);
		Arrays.sort(timeSlots, SORT_BY_ASCENDING_TIME);
		return Arrays.asList(timeSlots);
	}



	@Override
	public SortedMap<Long, List<FilledVolunteerRequisition>> findVolunteersForTimeSlotsByPosition(
			Long positionId) {
		SortedMap<Long, List<FilledVolunteerRequisition>> mapOfTimeSlotVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();

		Position position = getJpaTemplate().find(Position.class, positionId);
		String queryString = "select t from TimeSlot t where t.position = ?1";
		List timeSlots = getJpaTemplate().find(queryString, position);
		for (Object t : timeSlots) {
			TimeSlot timeSlot = (TimeSlot) t;
			queryString = "select f from FilledVolunteerRequisition f where f.timeSlot =?1";
			List resultList = getJpaTemplate().find(queryString, timeSlot);
			if (resultList.isEmpty()) {
				mapOfTimeSlotVolunteers.put(timeSlot.getId(),
						new ArrayList<FilledVolunteerRequisition>());
			} else {
				for (Object v : resultList) {
					FilledVolunteerRequisition volunteer = (FilledVolunteerRequisition) v;
					if (mapOfTimeSlotVolunteers.containsKey(timeSlot.getId())) {
						List<FilledVolunteerRequisition> volunteers = mapOfTimeSlotVolunteers
								.get(timeSlot.getId());
						volunteers.add(volunteer);
					} else {
						List<FilledVolunteerRequisition> volunteers = new ArrayList<FilledVolunteerRequisition>();
						volunteers.add(volunteer);
						mapOfTimeSlotVolunteers.put(timeSlot.getId(),
								volunteers);
					}
				}
			}
			
		}
		return mapOfTimeSlotVolunteers;		
	}



	@Override
	public SortedMap<Long, List<FilledVolunteerRequisition>> findVolunteersForPositionWithoutTimeSlots(
			Long positionId) {
		SortedMap<Long, List<FilledVolunteerRequisition>> mapOfPositionVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();

		Position position = getJpaTemplate().find(Position.class, positionId);
		String queryString = "select f from FilledVolunteerRequisition f where f.position = ?1 AND f.timeSlot IS NULL";
		List resultList = getJpaTemplate().find(queryString, position);
		if (resultList.isEmpty()) {
			mapOfPositionVolunteers.put(position.getId(),
						new ArrayList<FilledVolunteerRequisition>());
		} else {
			for (Object v : resultList) {
				FilledVolunteerRequisition volunteer = (FilledVolunteerRequisition) v;
				if (mapOfPositionVolunteers.containsKey(position.getId())) {
					List<FilledVolunteerRequisition> volunteers = mapOfPositionVolunteers
							.get(position.getId());
						volunteers.add(volunteer);
				} else {
					List<FilledVolunteerRequisition> volunteers = new ArrayList<FilledVolunteerRequisition>();
					volunteers.add(volunteer);
					mapOfPositionVolunteers.put(position.getId(),
								volunteers);
				}
			}
		}
					
		return mapOfPositionVolunteers;		
	}



	@Override
	public FilledVolunteerRequisition findVolunteerRequisition(Long eventId,
			Long userId, Long positionId, Long timeSlotId) {
		Event storedEvent = getJpaTemplate().find(Event.class, eventId);
		Position storedPosition = getJpaTemplate().find(Position.class, positionId);
		User storedUser = getJpaTemplate().find(User.class, userId);
		FilledVolunteerRequisition volunteer = null;
		if (timeSlotId != null) { 
			TimeSlot storedTimeSlot = getJpaTemplate().find(TimeSlot.class, timeSlotId);
		
			List resultList = getJpaTemplate().find("select f from  FilledVolunteerRequisition f where f.event = ?1 AND f.position = ?2 AND f.timeSlot = ?3 AND f.user = ?4",
				storedEvent, storedPosition, storedTimeSlot, storedUser);
			if (!resultList.isEmpty()) volunteer = (FilledVolunteerRequisition) resultList.get(0);
		} else {
			List resultList = getJpaTemplate().find("select f from  FilledVolunteerRequisition f where f.event = ?1 AND f.position = ?2 AND f.timeSlot IS NULL AND f.user = ?4",
				storedEvent, storedPosition, storedUser);
			if (!resultList.isEmpty()) volunteer = (FilledVolunteerRequisition) resultList.get(0);
		}
		return volunteer;
	}



	@SuppressWarnings("unchecked")
	@Override
	public List<FilledVolunteerRequisition> findVolunteersForTimeSlot(
			Long timeSlotId) {
		TimeSlot storedTimeSlot = getJpaTemplate().find(TimeSlot.class, timeSlotId);
		List resultList = getJpaTemplate().find("select f from  FilledVolunteerRequisition f where f.timeSlot = ?1",
			storedTimeSlot);
		return resultList;
	}

	public boolean registerForPosition(Long positionId, Long userId) {
		
		Position storedPosition = getJpaTemplate().find(Position.class, positionId);
		Event storedEvent = getJpaTemplate().find(Event.class, storedPosition.getEvent().getId());
		User storedUser = getJpaTemplate().find(User.class, userId);
		List resultFilledPositions = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.user = ?1 AND f.position = ?2 AND f.timeSlot IS NULL", storedUser, storedPosition);
		if (!resultFilledPositions.isEmpty()) {
			throw new NonUniqueResultException("You have already volunteered for position '" + storedPosition.getName() + "' at event '" + storedEvent.getName()+"'.");
		}
		
		// check if filled
		resultFilledPositions = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.position = ?1 AND f.timeSlot IS NULL", storedPosition);
		
		if (resultFilledPositions.size() == storedPosition.getRequiredNumberOfVolunteers()) {
			throw new NoResultException("Unable to volunteer for position '"+storedPosition.getName()+"' at event '" + storedEvent.getName()+"'. The position has been filled.");
		}
		
		FilledVolunteerRequisition fvr = new FilledVolunteerRequisition(storedUser, storedEvent, storedPosition);
		getJpaTemplate().persist(fvr);
		return true;
	}



	@Override
	public FilledVolunteerRequisition findVolunteerPosition(Long positionId,
			Long userId) {
		Position storedPosition = getJpaTemplate().find(Position.class, positionId);
		Event storedEvent = getJpaTemplate().find(Event.class, storedPosition.getEvent().getId());
		User storedUser = getJpaTemplate().find(User.class, userId);
		List resultFilledPositions = getJpaTemplate().
		find("select f from FilledVolunteerRequisition f where f.user = ?1 AND f.position = ?2 AND f.timeSlot IS NULL", storedUser, storedPosition);
		if (resultFilledPositions.isEmpty()) {
			throw new NoResultException("Unable to find volunteer position for position '"+storedPosition.getName()+"' at event '" + storedEvent.getName()+" with volunteer '" + storedUser.getEmailAddress() + "'.");
		}
		
		FilledVolunteerRequisition fvr = new FilledVolunteerRequisition(storedUser, storedEvent, storedPosition);
		getJpaTemplate().persist(fvr);
		fvr.setEvent(storedEvent);
		fvr.setPosition(storedPosition);
		fvr.setUser(storedUser);
		return fvr;
	}
}